Skip to content

refactor(core): deprecate raising_dispatch#17941

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 8 commits into
mainfrom
avara1986/deprecate_rasing_dispatch
May 11, 2026
Merged

refactor(core): deprecate raising_dispatch#17941
gh-worker-dd-mergequeue-cf854d[bot] merged 8 commits into
mainfrom
avara1986/deprecate_rasing_dispatch

Conversation

@avara1986
Copy link
Copy Markdown
Member

@avara1986 avara1986 commented May 7, 2026

Description

Deprecates and removes core.raising_dispatch in favor of an opt-in allow_raise=True flag on the existing core.dispatch API. Tightens the in-tree blocking-exception hierarchy and aligns LangChain non-stream wrappers to tag the LLM span on AI Guard block.

What changed

1. core.dispatch gains allow_raise=True

ddtrace/internal/core/event_hub.pydispatch() and dispatch_event() accept a keyword-only allow_raise: bool = False. When set, listener Exceptions propagate to the caller (first raiser wins; subsequent listeners are skipped). BaseException-derived exceptions (including DDBlockException) always propagate regardless of the flag — matching the existing default-deny semantics for blocks.

This collapses two near-duplicate dispatch entry points into one and lets call sites be explicit about whether listener exceptions should surface.

2. core.raising_dispatch removed

The symbol is removed from ddtrace.internal.core and event_hub.py. All in-tree call sites are migrated:

  • ddtrace/contrib/internal/langchain/patch.py — six before events (chat/llm × generate/agenerate/stream).
  • ddtrace/contrib/internal/openai/patch.py — four sites (sync before/after, async before/after).

The .sg rule and snapshot tests for core-raising-dispatch are removed since the symbol no longer exists.

3. New DDBlockException base class

ddtrace/internal/_exceptions.py introduces DDBlockException(BaseException) as the umbrella for any in-tree decision to abort the current operation (web-request blocking, AI Guard policy abort, future product blocks). BlockingException (ASM) and AIGuardAbortError (AI Guard) now derive from it.

This gives integrations a single, semantically-correct catch target for "the platform decided to block this call" — distinct from arbitrary user/library Exceptions. It still inherits from BaseException so a generic except Exception: cannot silently swallow a block decision.

4. LangChain non-stream wrappers: tag the LLM span on block

The four non-stream wrappers (traced_llm_generate, traced_llm_agenerate, traced_chat_model_generate, traced_chat_model_agenerate) now have an explicit except DDBlockException: arm that calls span.set_exc_info(*sys.exc_info()) before re-raising, in addition to the existing except Exception: arm.

Required because AIGuardAbortError derives from BaseException (via DDBlockException) and is therefore not caught by except Exception:. Without the explicit arm, blocked LangChain calls would emit an LLMObs span with no error info — leaving a hole between the AI Guard span (block decision) and the LLM span (no link back to the abort). Aligns LangChain's non-stream behavior with the contract from #17913 for openai (_patched_endpoint uses except BaseException as e: err = e; raise, then _traced_endpoint's finally calls set_exc_info).

5. OpenAIAIGuardAbortError: documented catchability asymmetry

The compound class inherits from both openai.UnprocessableEntityError (Exception-derived) and AIGuardAbortError (now BaseException-derived). Via MRO it remains Exception-derived — so except openai.APIError: blocks still work for users migrating from non-AI-Guard error handling. The asymmetry vs plain AIGuardAbortError is now spelled out in an AIDEV-NOTE on the class. Code that wants uniform block detection across providers should branch on isinstance(e, AIGuardAbortError).

Testing

  • tests/internal/test_context_events_api.py — new coverage for dispatch(..., allow_raise=True) semantics: Exceptions propagate when set, are swallowed when not, listeners short-circuit on first raise, BaseException-derived listener exceptions (DDBlockException subclasses) always propagate regardless of the flag, the dispatch_event variant behaves identically, and the new exception inheritance is asserted.
  • tests/appsec/ai_guard/langchain/test_langchain.py — two new tests pin the set_exc_info-on-block contract on the LLM span (sync chat.invoke + async chat.ainvoke). A regression that swaps the explicit except DDBlockException arm back to except Exception would surface immediately.
  • Local runs:
    • appsec::ai_guard_langchain venv 5484ca0 (Python 3.13) — passes.
    • appsec::ai_guard_openai venv 1224d93 (Python 3.12) — passes (this was the suite that initially exposed the missing openai-contrib migration).
  • hatch run lint:fmt clean on all modified files.

Risks

  • AIGuardAbortError inheritance change is user-visible. It now derives from DDBlockException(BaseException) instead of Exception. User code today doing try: model.invoke(...) except Exception: ... to handle aborts will silently stop catching them. This is intentional (the BaseException derivation is what prevents accidental swallowing of blocks) but it does break code that relied on the prior behavior. Spelled out in the release note's upgrade section. The OpenAI-compatible variant (OpenAIAIGuardAbortError) remains catchable by except Exception: via MRO for SDK compatibility.
  • core.raising_dispatch removal. Internal helper under ddtrace.internal.core — not part of the public API. All in-tree call sites are migrated in this PR. The release note's deprecations section documents the migration path for any out-of-tree consumer that imported it.
  • Trace shape change on blocked LangChain non-stream calls. The LLM span is now finished with error == 1 and error.type containing AIGuardAbortError. Downstream consumers that filter LLM spans by error == 0 will see blocked LangChain calls as errored — desired observability change per chore(ai_guard): add OpenAI SDK integration (streaming) #17913 but worth flagging.
  • No public API breakage on core.dispatch. allow_raise is keyword-only, defaults to False, preserves existing non-raising behavior for every existing caller.

Additional Notes

Release note: releasenotes/notes/aiguard-abort-baseexception-deprecate-raising-dispatch-3a5c7f61ee4d2e1b.yaml covers the AIGuardAbortError inheritance change (upgrade section) and the raising_dispatch removal (deprecations section).

@cit-pr-commenter-54b7da
Copy link
Copy Markdown

cit-pr-commenter-54b7da Bot commented May 7, 2026

Codeowners resolved as

.github/workflows/system-tests.yml                                      @DataDog/python-guild @DataDog/apm-core-python
.gitlab-ci.yml                                                          @DataDog/python-guild @DataDog/apm-core-python
ddtrace/appsec/_ai_guard/_langchain.py                                  @DataDog/asm-python
ddtrace/appsec/_ai_guard/_openai.py                                     @DataDog/asm-python
ddtrace/appsec/ai_guard/_api_client.py                                  @DataDog/asm-python
ddtrace/contrib/internal/langchain/patch.py                             @DataDog/ml-observability
ddtrace/contrib/internal/openai/patch.py                                @DataDog/ml-observability
ddtrace/internal/_exceptions.py                                         @DataDog/asm-python
ddtrace/internal/core/__init__.py                                       @DataDog/apm-core-python
ddtrace/internal/core/event_hub.py                                      @DataDog/apm-core-python
releasenotes/notes/aiguard-abort-baseexception-deprecate-raising-dispatch-3a5c7f61ee4d2e1b.yaml  @DataDog/apm-python
tests/appsec/ai_guard/langchain/test_langchain.py                       @DataDog/asm-python
tests/appsec/ai_guard/openai/test_openai.py                             @DataDog/asm-python
tests/internal/test_context_events_api.py                               @DataDog/apm-core-python

@datadog-prod-us1-3

This comment has been minimized.

@pr-commenter
Copy link
Copy Markdown

pr-commenter Bot commented May 7, 2026

Benchmarks

Benchmark execution time: 2026-05-11 08:10:55

Comparing candidate commit f179fff in PR branch avara1986/deprecate_rasing_dispatch with baseline commit d1a7113 in branch main.

Found 0 performance improvements and 7 performance regressions! Performance is the same for 588 metrics, 4 unstable metrics.

scenario:iast_aspects-re_search_aspect

  • 🟥 execution_time [+19.191µs; +25.129µs] or [+7.385%; +9.670%]

scenario:iastaspects-lstrip_aspect

  • 🟥 execution_time [+74.630µs; +85.273µs] or [+27.962%; +31.949%]

scenario:iastaspects-repr_aspect

  • 🟥 execution_time [+50.495µs; +57.999µs] or [+16.036%; +18.419%]

scenario:iastaspects-upper_aspect

  • 🟥 execution_time [+46.635µs; +54.237µs] or [+15.567%; +18.104%]

scenario:iastaspectsospath-ospathbasename_aspect

  • 🟥 execution_time [+93.399µs; +104.069µs] or [+23.301%; +25.963%]

scenario:span-start

  • 🟥 execution_time [+1.236ms; +1.391ms] or [+7.957%; +8.949%]

scenario:telemetryaddmetric-1-count-metric-1-times

  • 🟥 execution_time [+209.343ns; +234.358ns] or [+10.111%; +11.320%]

@avara1986 avara1986 added the changelog/no-changelog A changelog entry is not required for this PR. label May 7, 2026
@avara1986 avara1986 force-pushed the avara1986/deprecate_rasing_dispatch branch from df478a5 to a96248a Compare May 8, 2026 09:23
@avara1986
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 53f175fd3d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ddtrace/internal/core/__init__.py
@avara1986 avara1986 changed the title chore(core): depreacate raising_dispatch chore(core): deprecate raising_dispatch May 8, 2026
Replace ``core.raising_dispatch`` with a keyword-only ``allow_raise=True``
flag on ``core.dispatch`` (and ``dispatch_event``). Migrate all in-tree
call sites in the langchain and openai contribs.

Introduce ``DDBlockException(BaseException)`` as the umbrella class for
in-tree block decisions. ``BlockingException`` (ASM) and
``AIGuardAbortError`` now derive from it. ``AIGuardAbortError``'s base
moves from ``Exception`` to ``DDBlockException`` so a generic
``except Exception:`` no longer silently swallows a block — release note
spells out the upgrade impact for users.

Tag the LLM span on AI Guard block in the four LangChain non-stream
wrappers: an explicit ``except DDBlockException:`` arm calls
``span.set_exc_info`` before re-raising, since the existing
``except Exception:`` does not catch ``BaseException``-derived aborts.
Mirrors the contract from #17913 (openai ``_patched_endpoint``).

Document the catchability asymmetry on ``OpenAIAIGuardAbortError``: it
inherits from ``openai.UnprocessableEntityError`` (Exception-derived)
*and* ``AIGuardAbortError`` (BaseException-derived), so that subclass
remains catchable by ``except Exception:`` for OpenAI SDK compatibility,
while plain ``AIGuardAbortError`` is not.

Tests:
- ``tests/internal/test_context_events_api.py`` — coverage for
  ``dispatch(..., allow_raise=True)`` semantics, ``BaseException``
  propagation, and the new exception-class hierarchy.
- ``tests/appsec/ai_guard/langchain/test_langchain.py`` — pin the
  LLM-span ``set_exc_info`` contract on AI Guard block (sync + async).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@avara1986 avara1986 force-pushed the avara1986/deprecate_rasing_dispatch branch from 1cc763c to 8a5cb85 Compare May 8, 2026 10:12
@avara1986
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b9eb78aa6a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ddtrace/contrib/internal/openai/patch.py Outdated
@avara1986 avara1986 marked this pull request as ready for review May 8, 2026 19:55
@avara1986 avara1986 requested review from a team as code owners May 8, 2026 19:55
@avara1986 avara1986 requested review from emmettbutler and wantsui May 8, 2026 19:55
@avara1986 avara1986 changed the title chore(core): deprecate raising_dispatch refactor(core): deprecate raising_dispatch May 8, 2026
@avara1986 avara1986 removed the changelog/no-changelog A changelog entry is not required for this PR. label May 8, 2026
Comment thread ddtrace/contrib/internal/langchain/patch.py
Comment thread ddtrace/contrib/internal/langchain/patch.py
Copy link
Copy Markdown
Contributor

@christophe-papazian christophe-papazian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allow_raise may bypass subsequent listeners, while raising_dispatch was always running all listeners. This is a big change, do we really want that ?

@avara1986
Copy link
Copy Markdown
Member Author

allow_raise may bypass subsequent listeners, while raising_dispatch was always running all listeners. This is a big change, do we really want that ?

@christophe-papazian Yes, at least we aren't changing the behavior of the existing listeners (and if you check the code, the listeners were changed to raise an error instead of returning one)

@avara1986
Copy link
Copy Markdown
Member Author

/merge

@gh-worker-devflow-routing-ef8351
Copy link
Copy Markdown

gh-worker-devflow-routing-ef8351 Bot commented May 11, 2026

View all feedbacks in Devflow UI.

2026-05-11 12:04:58 UTC ℹ️ Start processing command /merge


2026-05-11 12:05:14 UTC ℹ️ MergeQueue: waiting for PR to be ready

This pull request is not mergeable according to GitHub. Common reasons include pending required checks, missing approvals, or merge conflicts — but it could also be blocked by other repository rules or settings.
It will be added to the queue as soon as checks pass and/or get approvals. View in MergeQueue UI.
Note: if you pushed new commits since the last approval, you may need additional approval.
You can remove it from the waiting list with /remove command.


2026-05-11 13:07:17 UTC ℹ️ MergeQueue: merge request added to the queue

The expected merge time in main is approximately 53m (p90).


2026-05-11 13:46:37 UTC ℹ️ MergeQueue: This merge request was merged

@gh-worker-dd-mergequeue-cf854d gh-worker-dd-mergequeue-cf854d Bot merged commit cac2739 into main May 11, 2026
1152 checks passed
@gh-worker-dd-mergequeue-cf854d gh-worker-dd-mergequeue-cf854d Bot deleted the avara1986/deprecate_rasing_dispatch branch May 11, 2026 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants